﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text.RegularExpressions;

using King.Extensions;

namespace King.Reflection {

    public static partial class MethodInfoExtensions {

        public static IEnumerable<MethodInfo> GetOverrides(this MethodInfo source) {
            if (!source.IsVirtual)
                return Enumerable.Empty<MethodInfo>();

            var baseDefinition = source.GetBaseDefinition();
            var parameterTypes = source.GetParameters().Select(o => o.ParameterType).ToArray();

            var result =
                from o in source.DeclaringType.ToLinkList(o => o.BaseType).Skip(1)
                let operation = o.Get<MethodInfo>(source.Name, parameterTypes).SingleOrDefault()
                where operation != null && operation.IsVirtual
                where operation.GetBaseDefinition().DeclaringType == baseDefinition.DeclaringType
                select operation;

            return result;
        }
        public static bool IsOverrideOf(this MethodInfo source, MethodInfo methodInfo) {
            if (!source.IsVirtual || !methodInfo.IsVirtual)
                return false;

            var sourceBase = source.GetBaseDefinition();
            var methodInfoBase = methodInfo.GetBaseDefinition();

            if (sourceBase != methodInfoBase)
                return false;

            return source.DeclaringType.IsSubclassOf(methodInfo.DeclaringType);
        }
    }

    public static partial class TypeExtensions {

        // tests
        public static bool IsAssignableFrom(this Type type, object instance) {
            if (instance == null)
                return type.CanBeNull();
            return type.IsAssignableFrom(instance.GetType());
        }
        public static bool IsSubclassOfOrIs<T>(this Type type) {
            return type.IsSubclassOfOrIs(typeof(T));
        }
        public static bool IsSubclassOf<T>(this Type type) {
            return type.IsSubclassOf(typeof(T));
        }
        public static bool Implements<T>(this Type type) {
            return Implements(type, typeof(T));
        }
        public static bool Implements(this Type type, Type iface) {
            return type.GetInterfaces().Contains(iface);
        }
        public static bool IsStruct(this Type type) {
            return type.IsValueType && !type.IsPrimitive && !type.IsEnum && type != typeof(decimal);
        }
        public static bool IsEnumerable(this Type type) {
            return typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
        }
        public static bool IsFlagsEnum(this Type type) {
            return type.IsEnum && type.IsDefined<FlagsAttribute>();
        }
        public static bool IsSimpleEnum(this Type type) {
            return type.IsEnum && !type.IsDefined<FlagsAttribute>();
        }

        // generics
        public static Type GetGenericTypeDefinitionOrDefault(this Type type) {
            if (type.IsGenericTypeDefinition)
                return type.GetGenericTypeDefinition();
            return null;
        }
        public static Type GetGenericTypeDefinitionOrType(this Type type) {
            if (type.IsGenericType)
                return type.GetGenericTypeDefinition();
            return type;
        }
        public static IEnumerable<Type> GetGenericArgumentsOrEmpty(this Type type) {
            if (type.IsGenericType)
                return Type.EmptyTypes;
            return type.GetGenericArguments();
        }
        public static Type GreatestCommonBaseType(this IEnumerable<Type> types) {
            if (types.None())
                return null;

            var hierarchy = types.SelectMany(o => o.ToLinkList<Type>(x => x.BaseType));
            var candidates = hierarchy.Union(typeof(object)).Distinct();
            var commonBaseTypes = candidates.Where(o => types.All(x => o == x || x.IsSubclassOf(o)));
            var greatestCommonBaseType = commonBaseTypes.Where(o => commonBaseTypes.All(x => o == x || o.IsSubclassOf(x)));
            var result = greatestCommonBaseType.Single();

            return result;
        }

        // default
        private static object GetDefault<T>(Type type) {
            if (typeof(T) == type)
                return default(T);
            var methodInfo = MethodBase.GetCurrentMethod() as MethodInfo;
            return methodInfo.MakeGenericMethod(type).Invoke(null, new object[] { type });
        }
        public static object GetDefaultValue(this Type type) {
            return GetDefault<object>(type);
        }
        public static bool IsDefaultValue(this object source) {
            if (source == null)
                return true;

            return source.Equals(GetDefaultValue(source.GetType()));
        }

        // elementType
        public static Type GetEnumerationElementType(this Type type) {

            if (type == typeof(string))
                return null;

            var result =
               (from o in type.GetInterfaces().Union(type)
                where o.IsInstantiationOf(typeof(IEnumerable<>))
                select o.GetGenericArguments()[0])
                .GreatestCommonBaseType();

            if (result != null)
                return result;

            if (typeof(IEnumerable).IsAssignableFrom(type))
                return typeof(object);

            return null;
        }

        // make
        public static Type UnNullify(this Type type) {
            if (type.IsInstantiationOf(typeof(Nullable<>)))
                return type.GetGenericArguments().Single();
            return type;
        }

        public static Type MakeCanBeNull(this Type type) {
            if (type.CanBeNull())
                return type;
            return type.MakeNullable();
        }
        public static Type MakeEnumerable(this IEnumerable<Type> types) {
            return typeof(IEnumerable<>).MakeGenericType(types.GreatestCommonBaseType());
        }
        public static Type MakeEnumerable(this Type type) {
            return typeof(IEnumerable<>).MakeGenericType(type);
        }
        public static Type MakeList(this Type type) {
            return typeof(List<>).MakeGenericType(type);
        }
    }

    public static partial class MemberInfoExtensions {

        public static object GetPropertyOrFieldValue(this MemberInfo memberInfo, object instance) {
            FieldInfo fieldInfo = memberInfo as FieldInfo;
            PropertyInfo propertyInfo = memberInfo as PropertyInfo;

            if (fieldInfo != null)
                return fieldInfo.GetValue(instance);
            else
                return propertyInfo.GetValue(instance, null);
        }
        public static T GetBase<T>(this MemberInfo propertyOrMethod) where T : MemberInfo {
            var propertyInfo = propertyOrMethod as PropertyInfo;
            var methodInfo = propertyOrMethod as MethodInfo;

            if (propertyInfo != null)
                methodInfo = propertyInfo.GetGetMethod(true) ?? propertyInfo.GetSetMethod(true);

            if (methodInfo == null)
                throw new InvalidOperationException();

            var baseDefinition = methodInfo.GetBaseDefinition();

            if (typeof(T) == typeof(PropertyInfo))
                return (T)(object)baseDefinition.GetProperty();

            return (T)(object)baseDefinition;
        }

        // tests
        public static bool IsEqualTo(this MemberInfo source, MemberInfo target) {
            if (source.GetAssembly() != target.GetAssembly())
                return false;

            return source.MetadataToken == target.MetadataToken;
        }
        public static bool CanRead(this MemberInfo propertyOrField) {
            if (propertyOrField is FieldInfo)
                return true;

            var propertyInfo = propertyOrField as PropertyInfo;
            if (propertyInfo == null)
                throw new ArgumentException();

            return propertyInfo.CanRead;
        }
        public static bool CanWrite(this MemberInfo propertyOrField) {
            if (propertyOrField is FieldInfo)
                return true;

            var propertyInfo = propertyOrField as PropertyInfo;
            if (propertyInfo == null)
                throw new ArgumentException();

            return propertyInfo.CanWrite;
        }
        public static bool CanReadWrite(this MemberInfo propertyOrField) {
            if (propertyOrField is FieldInfo)
                return true;

            var propertyInfo = propertyOrField as PropertyInfo;
            if (propertyInfo == null)
                throw new ArgumentException();

            return propertyInfo.CanRead && propertyInfo.CanWrite;
        }
        public static bool IsVirtual(this MemberInfo propertyOrMethod) {
            PropertyInfo propertyInfo = propertyOrMethod as PropertyInfo;
            MethodBase methodBase = propertyOrMethod as MethodBase;

            if (propertyInfo != null)
                methodBase = propertyInfo.GetGetMethod(true) ?? propertyInfo.GetSetMethod(true);

            if (methodBase != null)
                return methodBase.IsVirtual;

            return false;
        }
        public static bool IsOpenVirtual(this MemberInfo propertyOrMethod) {
            PropertyInfo propertyInfo = propertyOrMethod as PropertyInfo;
            MethodBase methodBase = propertyOrMethod as MethodBase;

            if (propertyInfo != null)
                methodBase = propertyInfo.GetGetMethod(true) ?? propertyInfo.GetSetMethod(true);

            if (methodBase != null)
                return methodBase.IsVirtual && !methodBase.IsFinal;

            return false;
        }
        public static bool IsFinalVirtual(this MemberInfo propertyOrMethod) {
            PropertyInfo propertyInfo = propertyOrMethod as PropertyInfo;
            MethodBase methodBase = propertyOrMethod as MethodBase;

            if (propertyInfo != null)
                methodBase = propertyInfo.GetGetMethod(true) ?? propertyInfo.GetSetMethod(true);

            if (methodBase != null)
                return methodBase.IsVirtual && methodBase.IsFinal;

            return false;
        }
        public static bool IsAbstract(this MemberInfo propertyOrMethod) {
            PropertyInfo propertyInfo = propertyOrMethod as PropertyInfo;
            MethodBase methodBase = propertyOrMethod as MethodBase;

            if (propertyInfo != null)
                methodBase = propertyInfo.GetGetMethod(true) ?? propertyInfo.GetSetMethod(true);

            if (methodBase != null)
                return methodBase.IsAbstract;

            return false;
        }
        public static bool IsStatic(this MemberInfo memberInfo) {
            PropertyInfo propertyInfo = memberInfo as PropertyInfo;
            FieldInfo fieldInfo = memberInfo as FieldInfo;
            Type type = memberInfo as Type;

            if (propertyInfo != null)
                return propertyInfo.GetGetMethod(true).IsStatic;
            else if (fieldInfo != null)
                return fieldInfo.IsStatic;
            else if (type != null)
                return type.IsClass && type.IsSealed && !type.Get<ConstructorInfo>(isInstance: true).Any();

            throw new InvalidOperationException();
        }

        // activation\latebound
        //public static Action<object, object> ToSetValueDelegate(this MemberInfo memberInfo) {
        //    if (!(memberInfo is PropertyInfo || memberInfo is FieldInfo))
        //        throw new ArgumentException();

        //    var methodInfo = MethodInfo.GetCurrentMethod().DeclaringType.Get<MethodInfo>(
        //        "SetValue", typeof(MemberInfo), typeof(object), typeof(object));
        //    return (Action<object, object>)Delegate.CreateDelegate(typeof(Action<object, object>), memberInfo, methodInfo);
        //}
        //public static Func<object, object> ToGetValueDelegate(this MemberInfo memberInfo) {

        //    if (!(memberInfo is PropertyInfo || memberInfo is FieldInfo))
        //        throw new ArgumentException();

        //    var methodInfo = MethodInfo.GetCurrentMethod().DeclaringType.Get<MethodInfo>(
        //        "GetValue", typeof(MemberInfo), typeof(object));
        //    return (Func<object, object>)Delegate.CreateDelegate(typeof(Func<object, object>), memberInfo, methodInfo);
        //}
        public static object InvokeStatic(this MethodInfo methodInfo, params object[] arguments) {
            return methodInfo.Invoke(null, arguments);
        }
        public static object GetValue(this MemberInfo memberInfo) {
            return memberInfo.GetValue(null);
        }
    }

    public static class GrabBagExtensions {

        public class EqualityComparer<T, TKey> : IEqualityComparer<T> {

            #region Private Data Members
            private Func<T, TKey> m_keySelector;
            #endregion

            #region Constructor
            internal EqualityComparer(Func<T, TKey> keySelector) {
                m_keySelector = keySelector;
            }
            #endregion

            #region IEqualityComparer<T> Members
            public bool Equals(T x, T y) {
                return m_keySelector(x).Equals(m_keySelector(y));
            }
            public int GetHashCode(T obj) {
                return m_keySelector(obj).GetHashCode();
            }
            #endregion
        }

        #region Type (generics, default, activation)
        public static IEnumerable<Enum> GetEnumValues(this Type type) {
            return Enum.GetValues(type).ToEnumerableOfObject().Select(o => Enum.ToObject(type, o)).Cast<Enum>();
        }

        // activation
        public static ConstructorInfo GetDefaultConstructorInfo(this Type type) {
            return type.GetConstructor(
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                Type.DefaultBinder,
                Type.EmptyTypes,
                new ParameterModifier[] { });
        }
        public static Func<object> GetDefaultCtor(this Type type) {
            return (Func<object>)delegate() {
                return Activator.CreateInstance(type, true);
            };
        }
        #endregion

        #region MemberInfo (PropertyInfo, MethodInfo, MemberInfo, ICustomAttributeProvider)
        public static bool References(this Assembly assembly, Assembly target) {
            return assembly.GetReferencedAssemblies().Any(o => o.Name == target.GetName().Name);
        }

        // PropertyInfo
        public static object GetValue(this PropertyInfo propertyInfo, object value) {
            return propertyInfo.GetValue(value, null);
        }

        // MethodBase
        public static IEnumerable<Type> GetParameterTypes(this MethodBase methodBase) {
            return methodBase.GetParameters().Select(o => o.ParameterType);
        }

        // MethodInfo
        public static bool IsNotImplemented(this MethodInfo methodInfo) {
            var methodBody = methodInfo.GetMethodBody();
            var il = methodBody.GetILAsByteArray();

            var nop = 0x00;
            var newObj = 0x73;
            var thrw = 0x7A;

            return il[0] == nop && il[1] == newObj &&
                (il[3] == thrw || il[4] == thrw || il[5] == thrw || il[6] == thrw);
        }
        public static object CreateDelegate(this MethodInfo methodInfo, Type type) {
            return Delegate.CreateDelegate(type, methodInfo);
        }
        public static T CreateDelegate<T>(this MethodInfo methodInfo) {
            return (T)CreateDelegate(methodInfo, typeof(T));
        }
        public static bool IsGenericMethodOf(this MethodInfo methodInfo, MethodInfo genericMethodDefinition) {
            if (!methodInfo.IsGenericMethod)
                return false;

            return methodInfo.GetGenericMethodDefinition() == genericMethodDefinition;
        }
        public static MethodInfo GetGenericMethodDefinitionOrMethod(this MethodInfo methodInfo) {
            if (methodInfo.IsGenericMethod)
                return methodInfo.GetGenericMethodDefinition();
            return methodInfo;
        }
        public static IEnumerable<Type> GetGenericArgumentsOrEmpty(this MethodInfo methodInfo) {
            if (methodInfo.IsGenericMethod)
                return methodInfo.GetGenericArguments();
            return Type.EmptyTypes;
        }
        public static MethodInfo MakeGenericMethodOrMethod(this MethodInfo methodInfo, IEnumerable<Type> types) {
            if (methodInfo.IsGenericMethod)
                return methodInfo.MakeGenericMethod(types.ToArray());
            if (types.Any())
                throw new ArgumentException();
            return methodInfo;
        }
        public static T Invoke<T>(this MethodInfo methodInfo, object o, IEnumerable<object> arguments) {
            try {
                return (T)methodInfo.Invoke(o, arguments.ToArray());
            } catch (TargetInvocationException e) {
                throw e.InnerException;
            }
        }

        // ICustomAttributeProvider
        public static bool IsDefinedOnHierarchy<T>(this ICustomAttributeProvider @this) {
            Debug.Assert(typeof(T).IsSubclassOf(typeof(Attribute)));

            return @this.IsDefined(typeof(T), true);
        }

        // IEnumerable<MethodBase>
        public static T Select<T>(this IEnumerable<T> source, params Type[] parameterTypes) where T : MethodBase {
            return (T)Select<T>(source, parameterTypes);
        }
        public static T Select<T>(this IEnumerable<T> source, IEnumerable<Type> parameterTypes) where T : MethodBase {
            if (!source.Any())
                return null;

            return (T)Type.DefaultBinder.SelectMethod(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
                source.ToArray(),
                parameterTypes.ToArray(),
                (ParameterModifier[])null);
        }
        public static T SelectToDelegate<T>(IEnumerable<MethodBase> @this, params Type[] parameterTypes) {
            var methodBase = @this.Select(parameterTypes);

            if (methodBase == null)
                return default(T);

            Debug.Assert(typeof(T).IsSubclassOf(typeof(Delegate)));

            var ctorInfo = methodBase as ConstructorInfo;
            if (ctorInfo != null) {

                var returnType = typeof(T).GetMethod("Invoke", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).ReturnType;

                DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, returnType, parameterTypes, true);
                ILGenerator il = dynamicMethod.GetILGenerator();
                for (int i = 0; i < parameterTypes.Length; i++)
                    il.Emit(OpCodes.Ldarg, i + 1);
                il.Emit(OpCodes.Newobj, ctorInfo);
                il.Emit(OpCodes.Ret);
                return (T)(object)dynamicMethod.CreateDelegate(typeof(T));
            }

            return (T)(object)Delegate.CreateDelegate(typeof(T), (MethodInfo)methodBase);
        }

        // CustomAttributeTypedArgument
        public static object ToObject(this CustomAttributeTypedArgument @this) {
            return @this.ArgumentType.IsEnum ? Enum.ToObject(@this.ArgumentType, @this.Value) : @this.Value;
        }
        #endregion
    }
}